﻿using System;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Data.SqlClient;
using System.Transactions;
using CoreLibrary.Sql.Connection;
using NLog;

namespace CoreLibrary.Sql.Linq {

    public class LinqConnectionProvider : BaseConnectionProvider, ILinqConnectionProvider {

        public bool DeferredLoading { get; set; }
        public DataLoadOptions DataLoadOptions { get; private set; }

        private MappingSource _mappingSource;
        private readonly TransactionOptions _transactionOptions;

        public LinqConnectionProvider() {
            DeferredLoading = false;
            DataLoadOptions = new DataLoadOptions();
            _transactionOptions = new TransactionOptions {
                Timeout         = TransactionManager.DefaultTimeout,
                IsolationLevel  = IsolationLevel.ReadCommitted
            };
        }

        public void ExecuteUpdate(Action<DataContext> updateQuery) {
            var context = AcquireDataContext();
            try {
                context.Connection.Open();
                updateQuery(context);
            } 
            finally {
                ReleaseDataContext(context);
            }
        }

        public void ExecuteUpdate(Action<DataContext, TransactionScope> transactionalUpdateQuery) {
            var context = AcquireDataContext();
            try {
                using (var ts = new TransactionScope(TransactionScopeOption.Required, _transactionOptions)) {
                    context.Connection.Open();
                    transactionalUpdateQuery(context, ts);
                }
            } 
            finally {
                ReleaseDataContext(context);
            }
        }

        public T ExecuteQuery<T>(Func<DataContext, T> selectQuery) {
            var context = AcquireDataContext();
            try {
                context.Connection.Open();
                return selectQuery(context);
            } 
            finally {
                ReleaseDataContext(context);
            }
        }

        public T ExecuteQuey<T>(Func<DataContext, TransactionScope, T> transactionalSelectQuery) {
            var context = AcquireDataContext();
            try {
                context.Connection.Open();
                using (var ts = new TransactionScope(TransactionScopeOption.Required, _transactionOptions)) {
                    return transactionalSelectQuery(context, ts);
                }
            } 
            finally {
                ReleaseDataContext(context);
            }
        }

        public DataContext AcquireDataContext() {
            if (_mappingSource == null) {
                lock (this) {
                    if (_mappingSource == null) {
                        var context = new DataContext(Acquire()) {
                            DeferredLoadingEnabled = DeferredLoading,
                            LoadOptions = DataLoadOptions
                        };
                        _mappingSource = context.Mapping.MappingSource;
                        return context;
                    }
                }
            }

            return new DataContext(Acquire(), _mappingSource) {
                LoadOptions = DataLoadOptions,
                DeferredLoadingEnabled = DeferredLoading
            };
        }

        public void ReleaseDataContext(DataContext context) {
            if (context == null) {
                return;
            }
            try {
                var connection = (SqlConnection) context.Connection;
                Release(connection);
                context.Dispose();
            } 
            catch (Exception error) {
                LogManager.GetCurrentClassLogger().ErrorException("Releasing data context failed - " + error.Message, error);
            }
        }
    }
}